#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import xml.dom.minidom
from xml.dom.minidom import parse, parseString

import Constraint, Interval
import DisplacementConstraint, AreaConstraint, RoleConstraint, SpeedConstraint, TimeConstraint
import ObjectLog, AssociatedInstance
import DB_Util
from Constraint import *
from Interval import *
from DisplacementConstraint import *
from AreaConstraint import *
from RoleConstraint import *
from SpeedConstraint import *
from TimeConstraint import *
from AssociatedInstance import *
from ObjectLog import *
from DB_Util import *

FRAMES = 25

class PedestrianCrossingAnalyzer:

    def __init__ (self, kb_file):

        self._instances = {}

        # Parse the file.
        try:
            doc = parse(kb_file)
        except IOError, e:
            print e
            sys.exit(0)

        root = doc.firstChild

        for instance in root.childNodes:
            if instance.nodeType == instance.ELEMENT_NODE and instance.nodeName == 'Instance':

                # Instance relevant information.
                instance_number = instance.getAttribute('number')

                for constraint in instance.childNodes:
                    if constraint.nodeType == constraint.ELEMENT_NODE:

                        # Role constraints.
                        if constraint.nodeName == 'Role_src':
                            try:
                                role_src = constraint.firstChild.nodeValue
                            except AttributeError:
                                role_src = None
                        elif constraint.nodeName == 'Role_dest':
                            try:
                                role_dest = constraint.firstChild.nodeValue
                            except AttributeError:
                                role_dest = None

                        # Speed constraints.
                        elif constraint.nodeName == 'SC1_speed_src':
                            for limit in constraint.childNodes:
                                if limit.nodeType == limit.ELEMENT_NODE:
                                    if limit.nodeName == 'low_limit':
                                        try:
                                            low_limit = limit.firstChild.nodeValue
                                        except AttributeError:
                                            low_limit = None
                                    elif limit.nodeName == 'high_limit':
                                        try:
                                            high_limit = limit.firstChild.nodeValue
                                        except AttributeError:
                                            high_limit = None
                                    elif limit.nodeName == 'extreme_limit':
                                        try:
                                            extreme_limit = limit.firstChild.nodeValue
                                        except AttributeError:
                                            extreme_limit = None

                        # SRC Area constraints.
                        elif constraint.nodeName == 'SC2_areas_src':
                            src_allowed_areas = []
                            src_forbidden_areas = []
                            for area in constraint.childNodes:
                                if area.nodeType == area.ELEMENT_NODE and area.nodeName == 'area':
                                    if area.getAttribute('allowed') == 'yes':
                                        src_allowed_areas.append(area.firstChild.nodeValue)
                                    else:
                                        src_forbidden_areas.append(area.firstChild.nodeValue)

                        # DEST Area constraints.
                        elif constraint.nodeName == 'SC3_areas_dest':
                            dest_allowed_areas = []
                            dest_forbidden_areas = []
                            for area in constraint.childNodes:
                                if area.nodeType == area.ELEMENT_NODE and area.nodeName == 'area':
                                    if area.getAttribute('allowed') == 'yes':
                                        dest_allowed_areas.append(area.firstChild.nodeValue)
                                    else:
                                        dest_forbidden_areas.append(area.firstChild.nodeValue)

                        # Displacement constraints.
                        elif constraint.nodeName == 'SC4_Ax_src':
                            for v in constraint.childNodes:
                                if v.nodeType == v.ELEMENT_NODE:
                                    if v.nodeName == 'growth':
                                        try:
                                            x_src_growth = v.firstChild.nodeValue
                                        except AttributeError:
                                            x_src_growth = None
                                    elif v.nodeName == 'value':
                                        try:
                                            x_src_value = v.firstChild.nodeValue
                                        except AttributeError:
                                            x_src_value = None

                        elif constraint.nodeName == 'SC5_Ay_src':
                            for v in constraint.childNodes:
                                if v.nodeType == v.ELEMENT_NODE:
                                    if v.nodeName == 'growth':
                                        try:
                                            y_src_growth = v.firstChild.nodeValue
                                        except AttributeError:
                                            y_src_growth = None
                                    elif v.nodeName == 'value':
                                        try:
                                            y_src_value = v.firstChild.nodeValue
                                        except AttributeError:
                                            y_src_value = None

                        elif constraint.nodeName == 'SC6_Ax_dest':
                            for v in constraint.childNodes:
                                if v.nodeType == v.ELEMENT_NODE:
                                    if v.nodeName == 'growth':
                                        try:
                                            x_dest_growth = v.firstChild.nodeValue
                                        except AttributeError:
                                            x_dest_growth = None
                                    elif v.nodeName == 'value':
                                        try:
                                            x_dest_value = v.firstChild.nodeValue
                                        except AttributeError:
                                            x_dest_value = None

                        elif constraint.nodeName == 'SC7_Ay_dest':
                            for v in constraint.childNodes:
                                if v.nodeType == v.ELEMENT_NODE:
                                    if v.nodeName == 'growth':
                                        try:
                                            y_dest_growth = v.firstChild.nodeValue
                                        except AttributeError:
                                            y_dest_growth = None
                                    elif v.nodeName == 'value':
                                        try:
                                            y_dest_value = v.firstChild.nodeValue
                                        except AttributeError:
                                            y_dest_value = None

                        # Time constraints.
                        elif constraint.nodeName == 'C_Time':
                            for time in constraint.childNodes:
                                if time.nodeType == limit.ELEMENT_NODE:
                                    if time.nodeName == 'low_limit':
                                        try:
                                            time_low_limit = time.firstChild.nodeValue
                                        except AttributeError:
                                            time_low_limit = None
                                    elif time.nodeName == 'high_limit':
                                        try:
                                            time_high_limit = time.firstChild.nodeValue
                                        except AttributeError:
                                            time_high_limit = None
                                    elif time.nodeName == 'extreme_limit':
                                        try:
                                            time_extreme_limit = time.firstChild.nodeValue
                                        except AttributeError:
                                            time_extreme_limit = None

                aux = Constraint(instance_number, 
                                 RoleConstraint(role_src), SpeedConstraint(Interval(low_limit, high_limit, extreme_limit)), 
                                 AreaConstraint(src_allowed_areas, src_forbidden_areas), 
                                 DisplacementConstraint('x', x_src_growth, x_src_value), 
                                 DisplacementConstraint('y', y_src_growth, y_src_value),

                                 RoleConstraint(role_dest), AreaConstraint(dest_allowed_areas, dest_forbidden_areas),
                                 DisplacementConstraint('x', x_dest_growth, x_dest_value), 
                                 DisplacementConstraint('y', y_dest_growth, y_dest_value),
                                 TimeConstraint(Interval(time_low_limit, time_high_limit, time_extreme_limit)))

                self._instances[instance_number] = aux

    def analyze (self, object_info_events):

        # For each monitored object o...
        for o in object_info_events._objects:
            # If the object is on area of interest...
            if self.is_object_on_area_of_interest:
                # Analyze its behavior...
                self.analyze_instance_object(object_info_events._camera, object_info_events._frame, o)

    def analyze_instance_object (self, camera, frame, obj):
        
        # Instant log for each single frame.
        log = ObjectLog(obj._object_id)

        # Associate one constraint instance per existing constraint.
        # The constraint id is the primary key of the dictionary.
        for k in self._instances.keys():
            log.add_instance(k, AssociatedInstance(k))

        # Source constraints.
        self.src_role_analysis(obj, log)
        self.src_speed_analysis(obj, camera, frame, log)
        self.src_areas_analysis(obj, log)
        self.src_displacement_x_analysis(obj, camera, frame, log)
        self.src_displacement_y_analysis(obj, camera, frame, log)

        # Destination constraints.
        self.dest_role_analysis(obj, log)
        self.dest_areas_analysis(obj, log)
        self.dest_displacement_x_analysis(obj, camera, frame, log)
        self.dest_displacement_y_analysis(obj, camera, frame, log)

        self.time_analysis(obj, log)

        # Update the satisfaction degrees of all the associated instances.
        log.update_satisfaction_degree()

        print str(log) + '\n'

        # TODO --> Udpate the DB.

    def src_role_analysis (self, obj, log):

        degree = 0.0

        # For each deployed constraint instance ins...
        for k, ins in self._instances.iteritems():
            for role in obj._roles:
                if ins._src_role == None:
                    degree = 1.0
                    break
                # Get the role whose degree of satisfaction is the highest one.
                if role._name == ins._src_role._role and role._degree > degree:
                    degree = role._degree

            # Update the associated instance k.
            log._instances[k]._src_role_satisfaction = degree

    def src_speed_analysis (self, obj, camera, frame, log):

        db = DB_Util()

        # For each deployed constraint instance ins...
        for k, ins in self._instances.iteritems():
            # Get the object speed.
            speed = db.get_object_speed (obj._object_id, camera, frame)
            # Calculate the degree of satisfaction.
            degree = ins._src_speed._interval.evaluate(speed)

            # Update the associated instance k.
            log._instances[k]._src_speed_satisfaction = degree

    def src_areas_analysis (self, obj, log):

        degree = 0.0

        # For each deployed constraint instance ins...
        for k, ins in self._instances.iteritems():
            for loc in obj._locations:
                # Get the area whose coverage is the highest one.
                if loc._area in ins._src_areas._allowed_areas and loc._degree > degree:
                    degree = loc._degree

            # Update the associated instance k.
            log._instances[k]._src_areas_satisfaction = degree

    def src_displacement_x_analysis (self, obj, camera, frame, log, precision = FRAMES):

        db = DB_Util()

        # For each deployed constraint instance ins...
        for k, ins in self._instances.iteritems():
            # Get the object displacement along the X axis during 'precision' frames.
            displacement_x = db.get_object_displacement (obj._object_id, camera, frame, precision, 'x')
            # Calculate the degree of satisfaction.
            degree = ins._src_displacement_x.evaluate(displacement_x)

            # Update the associated instance k.
            log._instances[k]._src_displacement_x_satisfaction = degree

    def src_displacement_y_analysis (self, obj, camera, frame, log, precision = FRAMES):

        db = DB_Util()

        for k, ins in self._instances.iteritems():
            displacement_y = db.get_object_displacement (obj._object_id, camera, frame, precision, 'y')
            degree = ins._src_displacement_y.evaluate(displacement_y)

            log._instances[k]._src_displacement_y_satisfaction = degree

    def dest_role_analysis (self, obj, log):

        degree = 0.0

        for k, ins in self._instances.iteritems():
            for role in obj._roles:            
                if ins._dest_role == None:
                    degree = 1.0
                    break 
                if role._name == ins._dest_role._role and role._degree > degree:
                    degree = role._degree

            log._instances[k]._dest_role_satisfaction = degree

    def dest_areas_analysis (self, obj, log):

        degree = 0.0

        for k, ins in self._instances.iteritems():
            for loc in obj._locations:
                if loc._area in ins._dest_areas._allowed_areas and loc._degree > degree:
                    degree = loc._degree

            log._instances[k]._dest_areas_satisfaction = degree

    def dest_displacement_x_analysis (self, obj, camera, frame, log, precision = FRAMES):

        db = DB_Util()

        for k, ins in self._instances.iteritems():
            displacement_x = db.get_object_displacement (obj._object_id, camera, frame, precision, 'x')
            degree = ins._dest_displacement_x.evaluate(displacement_x)

            log._instances[k]._dest_displacement_x_satisfaction = degree

    def dest_displacement_y_analysis (self, obj, camera, frame, log, precision = FRAMES):

        db = DB_Util()

        for k, ins in self._instances.iteritems():
            displacement_y = db.get_object_displacement (obj._object_id, camera, frame, precision, 'y')
            degree = ins._dest_displacement_y.evaluate(displacement_y)

            log._instances[k]._dest_displacement_y_satisfaction = degree

    # TODO: ¿Cómo calcular el tiempo que lleva un objeto en una zona?
    # Es decir, ¿cuándo se considera que empieza a cruzar?
    def time_analysis (self, obj, log):

        pass

    def is_object_on_area_of_interest (self, obj):

        for loc in obj._locations:
            for i in self._instances.values():
                if loc in i._src_areas or loc in i._dest_areas:
                    return True

        return False
